package com.jplus.core.utill;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import com.jplus.core.utill.LRUCache.Reload;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * HttpClient工具类
 * 
 * @author Yuanqy
 *
 */
public class HttpClientUtil {
	private final Logger LOG = LoggerFactory.getLogger(HttpClientUtil.class);

	/** 请求类型 */
	public static enum RequestMethod {
		GET, // 请求指定的页面信息，并返回内容主体。
		HEAD, // 类似于get请求，只不过返回的响应中没有具体的内容，用于获取报头
		POST, // 向指定资源提交数据进行处理请求（例如提交表单或者上传文件）。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
		PUT, // 从客户端向服务器传送的数据取代指定的文档的内容。
		DELETE, // 请求服务器删除指定的页面。
		CONNECT, // HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
		OPTIONS, // 允许客户端查看服务器的性能。
		TRACE, // 回显服务器收到的请求，主要用于测试或诊断。
		PATCH, // 实体中包含一个表，表中说明与该URI所表示的原内容的区别。
		MOVE, // 请求服务器将指定的页面移至另一个网络地址。
		COPY, // 请求服务器将指定的页面拷贝至另一个网络地址。
		LINK, // 请求服务器建立链接关系。
		UNLINK, // 断开链接关系。
		WRAPPED;// 允许客户端发送经过封装的请求。
	}

	private List<Proxy> PROXYS = new ArrayList<>();
	private Map<String, String> HEAD = new ConcurrentHashMap<>();
	private int TIMEOUT;
	private String CHARSET;

	/** 设置超时 */
	public void setTimeout(int timeout) {
		this.TIMEOUT = timeout;
	}

	/** 设置请求头 */
	public void setHeader(String key, String value) {
		this.HEAD.put(key, value);
	}

	/** 设置编码格式 */
	public void setCharSet(String charSet) {
		CHARSET = charSet;
	}

	/** 添加代理 */
	public void addProxy(String host, int port) {
		Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)); // http 代理
		PROXYS.add(proxy);
	}

	AtomicInteger pi = new AtomicInteger(0);

	protected Proxy getProxy() {
		if (PROXYS.isEmpty())
			return null;
		pi.compareAndSet(PROXYS.size(), 0);
		return PROXYS.get(pi.getAndIncrement());
	}

	public void init() {
		this.PROXYS.clear();
		this.HEAD.clear();
		this.TIMEOUT = 60000;// def= 60秒
		this.CHARSET = "UTF-8";
		setHeader("accept", "*/*");
		setHeader("connection", "keep-alive");
		setHeader("Keep-Alive", "timeout=" + TIMEOUT);
		setHeader("user-agent", "Mozilla/5.0 (Jplus;Windows NT 10.0; Win64; x64)");
		setHeader("content-type", "application/x-www-form-urlencoded"); // default form
	}

	public HttpClientUtil() {
		init();
	}

	/** 向指定URL发送GET方法的请求 */
	public String doGet(String url, Map<String, Object> param) {
		return doRequestStr(url, buildParams(param), RequestMethod.GET);
	}

	public String doGet(String url) {
		return doRequestStr(url, null, RequestMethod.GET);
	}

	/** 向指定 URL 发送POST方法的请求 */
	public String doPost(String url, Map<String, Object> param) {
		return doPost(url, buildParams(param));
	}

	public String doPost(String url, String param) {
		return doRequestStr(url, (param), RequestMethod.POST);
	}

	/**
	 * 执行请求
	 */
	public InputStream doRequest(String url, String param, RequestMethod rm) {
		try {
			HttpURLConnection conn = reloadConnection(url, param, rm).get(url);
			if (conn.getResponseCode() == HttpURLConnection.HTTP_OK)
				return conn.getInputStream();
			else
				LOG.warn("\nrequest url:{}\nresponse:{code:{},message:{}}", url, conn.getResponseCode(),
						conn.getResponseMessage());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return null;
	}

	/** 下载文件 到本地 */
	public void doDownload(String url, String param, RequestMethod rm, File savefile) {
		InputStream input = doRequest(url, param, rm);
		doWriteFile(savefile, toByteArray(input));
	}

	/** build get 请求参数 */
	public String buildParams(Map<String, Object> param) {
		if (param == null)
			return "";
		StringBuilder sb = new StringBuilder(param.size() * 20);
		for (Entry<String, Object> en : param.entrySet())
			sb.append(urlEncode(en.getKey()) + "=" + urlEncode(en.getValue()) + "&");
		return sb.toString();
	}

	/** 写文件 到本地 */
	public void doWriteFile(File file, byte[] is) {
		FileOutputStream out = null;
		try {
			if (is == null)
				throw new NullPointerException("file byte[] is null");
			out = new FileOutputStream(file);
			out.write(is);
			out.flush();
			out.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/** 发送请求 */
	public String doRequestStr(String url, String param, RequestMethod rm) {
		try {
			InputStream is = doRequest(url, param, rm);
			byte[] bytes = toByteArray(is);
			if (bytes != null)
				return new String(bytes, CHARSET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return null;
	}

	public byte[] toByteArray(InputStream input) {
		if (input == null)
			return null;
		try {
			ByteArrayOutputStream output = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int n = 0;
			while (-1 != (n = input.read(buffer)))
				output.write(buffer, 0, n);
			// FIX:
			// input.close();
			return output.toByteArray();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private Reload<String, HttpURLConnection> reloadConnection(String url, String param, RequestMethod rm) {
		return new Reload<String, HttpURLConnection>() {
			@Override
			public HttpURLConnection get(String key) {
				return getConnection(url, param, rm);
			}
		};
	}

	private HttpURLConnection getConnection(String url, String param, RequestMethod rm) {
		HttpURLConnection conn = null;
		try {
			if (rm == RequestMethod.GET && param != null)
				url = url + "?" + (param);
			checkUrl(url);
			URL realUrl = new URL(url);
			Proxy proxy = getProxy();
			if (proxy == null)
				conn = (HttpURLConnection) realUrl.openConnection();
			else {
				LOG.info("use proxy:" + proxy.toString());
				conn = (HttpURLConnection) realUrl.openConnection(proxy);
			}
			conn.setConnectTimeout(TIMEOUT);// 毫秒
			conn.setReadTimeout(TIMEOUT);// 毫秒
			conn.setRequestMethod(rm.name());
			for (Entry<String, String> en : HEAD.entrySet())
				conn.setRequestProperty(en.getKey(), en.getValue());
			if (rm == RequestMethod.POST) {
				// 发送POST请求必须设置如下两行
				conn.setDoOutput(true);
				conn.setDoInput(true);
				PrintWriter out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream(), CHARSET));
				// 发送请求参数
				out.print((param));
				out.flush();
				// FIX:
				// out.close();
			} else
				conn.connect();

		} catch (Exception e) {
			throw new RuntimeException("getConnection ERROR:", e);
		}
		return conn;
	}

	private String urlEncode(Object param) {
		param = param == null ? "" : param;
		try {
			return URLEncoder.encode(param.toString().trim(), CHARSET);
		} catch (UnsupportedEncodingException e) {
			return param.toString();
		}
	}

	private List<String> SAFE = Arrays.asList("!#%$&'()*+,/:;=?@-._~".split(""));

	private void checkUrl(String url) {
		char[] urls = url.toCharArray();
		for (int i = 0; i < urls.length; i++) {
			char c = urls[i];
			if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
					|| SAFE.contains(String.valueOf(c)))
				continue;
			throw new RuntimeException(
					"Invalid character in request url. At here:\n" + url + "\n" + getPad(i, " ") + "^");
		}
	}

	private String getPad(int len, String def) {
		StringBuilder sb = new StringBuilder(len + 1);
		for (int i = 0; i < len; i++)
			sb.append(def);
		return sb.toString();
	}

}